package cn.boodqian.airreport;

import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import junit.framework.Assert;

import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

import cn.boodqian.utils.Log;
import cn.boodqian.utils.GAERequest;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.v4.app.NotificationCompat;
import android.text.format.DateUtils;
import android.util.SparseArray;
import android.widget.RemoteViews;
import android.widget.Toast;

public class AirreportWidgetProvider extends AppWidgetProvider {

	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {
		// To prevent any ANR timeouts, we perform the update in a service
		Intent intent = new Intent(context, UpdateService.class);
		intent.putExtra("widgetIds", appWidgetIds);
		context.startService(intent);
	}
	
	private static final String mWidgetLocInfoPrefix = "Widget_LocInfo";
	private static final String mWidgetDataPrefix = "Widget_Data";
	public static void saveWidgetPref(Context context, int widgetId, String locId, String locName) {
		SharedPreferences prefs = context.getSharedPreferences(GlobalData.gPrefName, Context.MODE_PRIVATE);
		Editor editor = prefs.edit();
		
		String key = mWidgetLocInfoPrefix + widgetId;
		editor.putString(key, locId + "|" + locName);
		editor.commit();
		Log.i(String.format("Saved %s - %s %s", key, locId, locName));
	}
	
	public static boolean loadWidgetPref(Context context, int widgetId, String []locInfo) {
		Assert.assertTrue(locInfo.length >= 2);
		SharedPreferences prefs = context.getSharedPreferences(GlobalData.gPrefName, Context.MODE_PRIVATE);
		
		String key = mWidgetLocInfoPrefix + widgetId;
		String locInfoStr = prefs.getString(key, "");
		String []locInfoTemp = locInfoStr.split("\\|");
		if( locInfoTemp.length >= 2 ) {
			locInfo[0] = locInfoTemp[0];
			locInfo[1] = locInfoTemp[1];
			Log.i(String.format("Loaded %s - %s %s", key, locInfo[0], locInfo[1]));
		} else {
			Log.e(String.format("Loaded %s failed, loaded string: '%s'", key, locInfoStr));
			return false;
		}
		return true;
	}

	public static void saveWidgetData(Context context, int widgetId, AirData airdata) {
		if( airdata == null ) return;
		
		SharedPreferences prefs = context.getSharedPreferences(GlobalData.gPrefName, Context.MODE_PRIVATE);
		Editor editor = prefs.edit();
		
		String key = mWidgetDataPrefix + widgetId;
		String datastr = GlobalData.gGson.toJson(airdata);
		editor.putString(key, datastr);
		editor.commit();
		Log.i(String.format("Saved %s", key));
		if( Log.isLoggable(Log.DEBUG) ) Log.d("datastr="+datastr);
	}
	
	public static AirData loadWidgetData(Context context, int widgetId) {
		SharedPreferences prefs = context.getSharedPreferences(GlobalData.gPrefName, Context.MODE_PRIVATE);
		
		String key = mWidgetDataPrefix + widgetId;
		String datastr = prefs.getString(key, "");
		AirData airdata = GlobalData.gGson.fromJson(datastr, AirData.class);
		Log.i(String.format("Loaded %s", key));
		if( Log.isLoggable(Log.DEBUG) ) Log.d("datastr="+datastr);
		return airdata;
	}
		
	public static class UpdateService extends Service {
		
		private class GAEHandler extends Handler {
			@Override
			public void handleMessage(Message msg) {
				switch(msg.what) {
				case GAERequest.MESSAGE_DONE_OK:
					newAirdataArrived((String)msg.obj);
					synchronized(UpdateService.this) {
						isRunning = false;
					}
					break;
				case GAERequest.MESSAGE_DONE_ERR:
				    newAirdataArrived("");
					Toast.makeText(
							UpdateService.this,
							getString(R.string.refresh_error) + "\n"
									+ request.getLastError(),
									Toast.LENGTH_LONG).show();
					synchronized(UpdateService.this) {
						isRunning = false;
					}
					break;
				}
			}
		}
		
		private GAERequest request = new GAERequest(GlobalData.gGAEProject, new GAEHandler());
		private SparseArray<String[]> widgetInfo = new SparseArray<String[]>();
		private SparseArray<AirData> widgetData = new SparseArray<AirData>();
		private SparseArray<RemoteViews> widgetViews = new SparseArray<RemoteViews>();
		private ArrayList<HashMap<String,String>> postData = new ArrayList<HashMap<String,String>>();
		private 	SimpleDateFormat fmt = new SimpleDateFormat("MM/dd HH:mm", java.util.Locale.US);
		private Boolean isRunning = false;
		
		@Override
		public IBinder onBind(Intent intent) {
			return null;
		}
		
		private static final int UPDATE_AIRDATA = 0;
		private static final int UPDATE_NODATA = 1;
		private static final int UPDATE_UPDATING = 2;
		private RemoteViews buildUpdate(int widgetId, AirData airdata, int status) {
			RemoteViews views = new RemoteViews(getPackageName(), R.layout.widget);
			int alpha = (int)(255f*(100-GlobalData.getPref_WidgetTranparency(this))/100f);
			if( alpha < 0 ) alpha = 0;
			if( alpha > 255 ) alpha = 255;
			Log.i("alpha: " + alpha);
			views.setInt(R.id.widget_backgroud, "setAlpha", alpha);
			
		    String [] locInfo = new String[]{"",""};
		    if( ! loadWidgetPref(this, widgetId, locInfo) ) {
		        views.setInt(R.id.widget_aqidesc, "setText", R.string.widget_invalid);
		        views.setTextColor(R.id.widget_aqidesc, Color.WHITE);
		        return views;
		    }
		    Log.i(String.format(java.util.Locale.US, "[%d - %s] Preparing", widgetId, locInfo[0]));
		    
			views.setTextViewText(R.id.widget_location, locInfo[1]);
			
			if( status == UPDATE_AIRDATA ) {
			    Object[] maxPollutant = GlobalData.getMaxPollutant(airdata, GlobalData.getPref_AQIStandard(this));
			    @SuppressWarnings("unused")
			    String polluteName = (String)maxPollutant[0];
			    Integer polluteAqi = (Integer)maxPollutant[1];
			    Float polluteConc = (Float)maxPollutant[2];

			    String time = "";
			    String aqi = "";
			    String aqiDesc = getString(R.string.widget_nodata);;
			    int aqiColor = Color.GRAY;
			    if( polluteConc.compareTo(0f) > 0 ) {
			        aqi = Integer.toString(polluteAqi);
			        aqiColor = getResources().getColor(AQI.AQIColor(polluteAqi));
			        aqiDesc = getString(AQI.AQICategory(polluteAqi));
			    }
			    if( airdata != null && airdata.getTime() != null ) {
			        time = fmt.format(airdata.getTime());
			    }

			    // Build the widget update
			    views.setTextViewText(R.id.widget_aqi, aqi);
			    views.setTextColor(R.id.widget_aqi, aqiColor);
			    views.setTextViewText(R.id.widget_aqidesc, aqiDesc);
			    views.setTextColor(R.id.widget_aqidesc, aqiColor);
			    views.setTextViewText(R.id.widget_time, time);
			} else if( status == UPDATE_NODATA ) {
			    views.setInt(R.id.widget_aqidesc, "setText", R.string.widget_nodata);
			}  else if( status == UPDATE_UPDATING ) {
			    views.setInt(R.id.widget_aqidesc, "setText", R.string.refresh_data);
			}
			
			// Update on click
			if( GlobalData.getPref_WidgetClick(this).equals( SettingsActivity.WIDGET_CLICK_REFRESH) ) {
			    Intent intent2 = new Intent(this, AirreportWidgetProvider.class);
			    intent2.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
			    intent2.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{widgetId});
			    PendingIntent pendingIntent2 = PendingIntent.getBroadcast(this, widgetId, intent2, 0);
			    views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent2);
			} else {
			    Intent intent2 = new Intent(this, AirreportProfileActivity.class);
			    intent2.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			    PendingIntent pendingIntent2 = PendingIntent.getActivity(this, 0, intent2, 0);
			    views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent2);
			}
			Log.i("onclick binding " + locInfo[0] + " - " + widgetId);
			
			return views;
		}
		
		@Override
		public int onStartCommand(Intent intent, int flags, int startId) {
        	Log.i("widget service started");
        	Bundle extras = intent.getExtras();
    		if( extras == null ) {
    			Log.w("no extras found");
    			return START_NOT_STICKY;
    		}
    		
    		// Synchronize protection
    		synchronized(UpdateService.this) {
    			if( isRunning ) {
				Toast.makeText(
						this,
						getString(R.string.refresh_running),
						Toast.LENGTH_LONG).show();
    				return START_NOT_STICKY;
    			}
    			isRunning = true;
    		}
    		
    		// Flag to indicate whether to reset isRunning (set to false)
    		boolean resetRunning = true;
    		try {
    			ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    			NetworkInfo ni = cm.getActiveNetworkInfo();
    			boolean isNetworkAvailable = true;
    			if(  ni == null || !ni.isAvailable() || !ni.isConnected() ) { // Network unavailable
    			    isNetworkAvailable = false;
    				Toast.makeText(
    						this,
    						getString(R.string.no_network),
    						Toast.LENGTH_LONG).show();
    			}
    			
    			int [] widgetIds = extras.getIntArray("widgetIds");
    			for (int widgetId : widgetIds) {
    				Log.i("list of widget IDs: " + widgetId);
    			}

    			// Prepare the query json string
    			postData.clear();
    			widgetInfo.clear();
    			widgetData.clear();
    			widgetViews.clear();
    			Date now = new Date();
    			for (int widgetId : widgetIds) {
    				AppWidgetManager manager = AppWidgetManager.getInstance(this);
    				String [] locInfo = new String[]{"",""};
    				if( ! loadWidgetPref(this, widgetId, locInfo) ) continue;
    				Log.i(String.format(java.util.Locale.US, "[%d - %s] Preparing", widgetId, locInfo[0]));
    				
    				// Build view for saved data
    				AirData airdata = loadWidgetData(this, widgetId);
    				RemoteViews oldviews = buildUpdate(widgetId, airdata, UPDATE_AIRDATA);
    				widgetViews.put(widgetId, oldviews);
    				manager.updateAppWidget(widgetId, oldviews);

    				if( airdata != null && airdata.getTime() != null &&
    						now.getTime() - airdata.getTime().getTime() < 3600000 + 1800000 ) {
    					// data still up-to-date
    					Log.i(String.format(java.util.Locale.US, "[%d - %s] Data still up-to-date", widgetId, locInfo[0]));
    					continue;
    				}

    				// (Not used for now)
    				widgetData.put(widgetId, airdata);

    				// Remember to update this
    				widgetInfo.put(widgetId, locInfo);

    				// If no network available, no update needed
    				if( isNetworkAvailable ) {
    				    // Show updating message on widget
    				    RemoteViews viewsUpdating = buildUpdate(widgetId, null, UPDATE_UPDATING);
    				    manager.updateAppWidget(widgetId, viewsUpdating);

    				    HashMap<String,String> map = new HashMap<String, String>();
    				    map.put("id", locInfo[0]);
    				    map.put("hour","1");
    				    postData.add(map);
    				}
    			}

    			// Widget pref data not saved yet
    			// Also will be true if no network available
    			if( postData.size() == 0 ) {
    				return START_NOT_STICKY;
    			}
    			
    			// Everything fine, reset will be done in GAEHandler
    			resetRunning = false;
    		} finally {
    			// Make sure the running protection is turned off before any returns
    			if( resetRunning ) {
    				synchronized(UpdateService.this) {
    					isRunning = false;
    				}
    			}
    		}
			
			new Thread() {
				@Override
				public void run() {
					request.doPost("/airdata", GlobalData.gGson.toJson(postData));
				}
			}.start();
			return START_NOT_STICKY;
        }
        
        private void newAirdataArrived(String ret) {
        	Type type = new TypeToken<HashMap<String,AirData>>(){}.getType();
        	HashMap<String,AirData> newDataMap = null;
        	try {
        		newDataMap = GlobalData.gGson.fromJson(ret, type);
        	} catch(JsonSyntaxException e) {
        		Toast.makeText(
        				this,
        				getString(R.string.data_format_error),
        				Toast.LENGTH_LONG).show();
        		Log.e(e.getLocalizedMessage());
        	} finally {
        		if( newDataMap == null || newDataMap.size() == 0 ) {
        			Log.w("Update failed");
        			for (int index = 0; index < widgetViews.size(); index++) {
        			    int widgetId = widgetViews.keyAt(index);
        				RemoteViews views = widgetViews.get(widgetId);
        				AppWidgetManager manager = AppWidgetManager.getInstance(this);
        				manager.updateAppWidget(widgetId, views);
        			}
        			return;
        		}
        	}

        	StringBuilder warn_locations_sb = new StringBuilder();
        	int notificationThreshold = GlobalData.getPref_NotificationThreshold(this);
        	for (int index = 0; index < widgetInfo.size(); index++) {
        	    int widgetId = widgetInfo.keyAt(index);
        		String [] locInfo = widgetInfo.get(widgetId);
        		AirData airdata = newDataMap.get(locInfo[0]);

        		// Build the widget update
        		RemoteViews views = null;
        		if( airdata == null || airdata.getTime() == null ) {
        			Log.i(String.format(java.util.Locale.US, "[%d - %s] Using old", widgetId, locInfo[0]));
        			views = widgetViews.get(widgetId);
        		} else {
        		    /**
        		     * Construct warn locations if warn condition is met
        			 * Warn conditions:
        			 * 1. since_last_warn > LEAST_NOTIFY_INTERVAL
        			 * 2. AQI > threshold
        			 * 3. not the same day as last warn time
        			 */
        		    AirData airdata_saved = widgetData.get(widgetId);
        		    Date now = new Date();
        		    Date lastWarnTime = new Date(0);
        		    if( airdata_saved != null && airdata_saved.lastWarnTime != null ) {
        		        lastWarnTime = airdata_saved.lastWarnTime;
        		    }
        		    long since_last_warn = now.getTime() - lastWarnTime.getTime();
        		    if(Log.isLoggable(Log.DEBUG)) Log.d("since_last_warn="+since_last_warn);
        		    
        		    // Remember last warn time
        		    airdata.lastWarnTime = lastWarnTime;
        		    
        		    final long LEAST_NOTIFY_INTERVAL = 12*DateUtils.HOUR_IN_MILLIS;
        		    if( since_last_warn >= LEAST_NOTIFY_INTERVAL &&
        		            !org.apache.commons.lang3.time.DateUtils.isSameDay(lastWarnTime, now) &&
        		            notificationThreshold > 0) {
        		        Object[] maxPollutant = GlobalData.getMaxPollutant(airdata, GlobalData.getPref_AQIStandard(this));
        		        @SuppressWarnings("unused")
        		        String polluteName = (String)maxPollutant[0];
        		        Integer polluteAqi = (Integer)maxPollutant[1];
        		        Float polluteConc = (Float)maxPollutant[2];
        		        if( polluteAqi >= notificationThreshold ) {
        		            if( warn_locations_sb.length() > 0 )
        		                warn_locations_sb.append(", ");
        		            warn_locations_sb.append(locInfo[1]);
        		            warn_locations_sb.append(": ");
        		            warn_locations_sb.append(getString(AQI.AQICategory(polluteAqi)));
        		            airdata.lastWarnTime = now;
        		        }
        		    }
        		    
        			Log.i(String.format(java.util.Locale.US, "[%d - %s] Refreshing new", widgetId, locInfo[0]));
        			saveWidgetData(this, widgetId, airdata);
        			views = buildUpdate(widgetId, airdata, UPDATE_AIRDATA);
        			widgetViews.put(widgetId, views);
        		}

        		// Push update for this widget to the home screen
        		AppWidgetManager manager = AppWidgetManager.getInstance(this);
        		manager.updateAppWidget(widgetId, views);
        	}
        	
        	// Notify the user if warning condition is met
        	if( warn_locations_sb.length() == 0 )
        	    return;
        	
        	String text = warn_locations_sb.toString();
        	NotificationCompat.Builder builder =
        	        new NotificationCompat.Builder(this)
        	.setSmallIcon(R.drawable.ic_launcher)
        	.setContentTitle(getString(R.string.notify_warn_title))
        	.setContentText(text)
        	.setDefaults(Notification.DEFAULT_ALL)
        	.setAutoCancel(true);

        	Intent intent = new Intent(this, AirreportProfileActivity.class);
        	PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        	if( pendingIntent != null )
        	    builder.setContentIntent(pendingIntent);

        	NotificationManager nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
        	Notification notify = builder.getNotification();
        	nm.notify(0, notify);
        }
	}
}
